import collections
import GameWorld
import geo2
import math
import service
import stackless

class WorldPathfinder(service.Service):
    __guid__ = 'svc.worldPathfinder'
    __dependencies__ = ['gps', 'portal']
    __notifyevents__ = ['OnClearEntityInputs',
     'OnEntityArrival',
     'ProcessEntityTeleport',
     'ProcessEntityDestroy']

    def __init__(self, *args, **kwargs):
        self.worldSpaceSvc = None
        self.entitiesPaths = {}
        self.entityDestinations = {}
        self.entitiesWaitingToCrossPortal = {}
        self.entitiesCrossingPortal = {}
        self.followers = collections.defaultdict(list)
        self.followerRanges = {}
        service.Service.__init__(self, *args, **kwargs)



    def Run(self, *etc):
        self.worldSpaceSvc = sm.GetService(('worldSpace' + boot.role.title()))
        self.gameComponentSvc = sm.GetService(('gameComponent' + boot.role.title()))



    def OnEntityArrival(self, args):
        (entid, successful,) = args
        entity = self.gameComponentSvc.GetEntityByEntID(entid)
        if not successful:
            self._ClearAllInputs(entity)
        elif (entid in self.entitiesWaitingToCrossPortal):
            instanceID = entity.GetWorldSpaceInstanceID()
            portalID = self.entitiesWaitingToCrossPortal[entid]
            del self.entitiesWaitingToCrossPortal[entid]
            portalTransform = self.portal.GetPortalTransform(instanceID, portalID)
            portalInvTransform = geo2.MatrixInverse(portalTransform)
            entityLocalPosition = geo2.Vec3Transform(entity.GetPosition(), portalInvTransform)
            entityLocalPosition = (entityLocalPosition[0],
             entityLocalPosition[1],
             -1)
            nextDestination = geo2.Vec3Transform(entityLocalPosition, portalTransform)
            entity.movement.PushMoveMode(GameWorld.StraightPathMode(nextDestination[:3], 0, False, True))
            instance = self.worldSpaceSvc.GetWorldSpaceInstance(instanceID)
            portal = instance.GetPortal(portalID)
            gameWorld = instance.GetGameWorld()
            if hasattr(gameWorld, 'AddPortalToCheck'):
                gameWorld.AddPortalToCheck(entity.movement, portal.GetGameWorldPortal())
                self.entitiesCrossingPortal[entid] = (gameWorld,
                 entity.movement,
                 portal.GetGameWorldPortal())
        else:
            sm.ChainEvent('ProcessClearEntityMovement', entid)



    def OnClearEntityInputs(self, entID):
        entity = self.gameComponentSvc.GetEntityByEntID(entID)
        if (entity is not None):
            self._ClearAllInputs(entity)



    def _ClearAllInputs(self, entity):
        entid = entity.entid
        if (entid in self.entitiesPaths):
            del self.entitiesPaths[entid]
        if (entid in self.entityDestinations):
            del self.entityDestinations[entid]
        if (entid in self.entitiesWaitingToCrossPortal):
            del self.entitiesWaitingToCrossPortal[entid]
        self._ClearCrossingPortalData(entid)
        for (targetEntID, followerList,) in self.followers.items():
            if (entid in followerList):
                followerList.remove(entid)
            if (len(followerList) == 0):
                del self.followers[targetEntID]

        if (entid in self.followerRanges):
            del self.followerRanges[entid]



    def _ClearCrossingPortalData(self, entid):
        if (entid in self.entitiesCrossingPortal):
            (gameWorld, avatar, gwPortal,) = self.entitiesCrossingPortal[entid]
            gameWorld.RemovePortalToCheck(avatar, gwPortal)
            del self.entitiesCrossingPortal[entid]



    def ProcessEntityDestroy(self, entID):
        if (entID in self.followers):
            followList = list(self.followers[entID])
            del self.followers[entID]
            for followerEntID in followList:
                entity = self.gameComponentSvc.GetEntityByEntID(followerEntID)
                self._ClearAllInputs(entity)

        for followList in self.followers.itervalues():
            if (entID in followList):
                followList.remove(entID)




    def ProcessEntityTeleport(self, entid, pos, rot, instanceID, oldInstanceID):
        entity = self.gameComponentSvc.GetEntityByEntID(entid)
        if (entid in self.entitiesWaitingToCrossPortal):
            del self.entitiesWaitingToCrossPortal[entid]
        self._ClearCrossingPortalData(entid)
        if ((entid in self.entitiesPaths) or ((entid in self.entityDestinations) or (entid in self.followerRanges))):
            self._GoToNextNode(entity)
        if (entid in self.followers):
            followerList = list(self.followers[entid])
            del self.followers[entid]
            for followerEntID in followerList:
                sourceEnt = self.gameComponentSvc.GetEntityByEntID(followerEntID)
                if sourceEnt:
                    self.SetFollowTarget(sourceEnt, entity, self.followerRanges[followerEntID])




    def _GetPathAcrossInstances(self, entity, toInstanceID, toPosition):
        portalsNeededToPath = self.gps.GetPortalsNeededToPath(entity, toInstanceID, toPosition)
        if (portalsNeededToPath is not None):
            self.entitiesPaths[entity.entid] = portalsNeededToPath
            self._GoToNextNode(entity)
            return True
        else:
            return False



    def PathToLocation(self, entity, toInstanceID, toPosition):
        if (toInstanceID == entity.GetWorldSpaceInstanceID()):
            entity.movement.PushMoveMode(GameWorld.PathToMode(toPosition, 0.1, False, True))
        else:
            self._GetPathAcrossInstances(entity, toInstanceID, toPosition)
            self.entityDestinations[entity.entid] = toPosition



    def SetFollowTarget(self, sourceEnt, targetEnt, range = 0.0):
        if (isinstance(sourceEnt.movement.GetActiveMoveMode(), GameWorld.FollowMode) and (sourceEnt.movement.GetActiveMoveMode().followTarget == targetEnt.movement)):
            return 
        if (targetEnt.GetWorldSpaceInstanceID() == sourceEnt.GetWorldSpaceInstanceID()):
            self._AddFollowerInfo(sourceEnt, targetEnt, range)
            sourceEnt.movement.PushMoveMode(GameWorld.FollowMode(targetEnt.movement, range, False, 0.85))
        elif self._GetPathAcrossInstances(sourceEnt, targetEnt.GetWorldSpaceInstanceID(), targetEnt.GetPosition()):
            self._AddFollowerInfo(sourceEnt, targetEnt, range)



    def _AddFollowerInfo(self, sourceEnt, targetEnt, range):
        if (targetEnt.entid not in self.followers):
            self.followers[targetEnt.entid] = []
        self.followers[targetEnt.entid].append(sourceEnt.entid)
        self.followerRanges[sourceEnt.entid] = range



    def _HasFollowTarget(self, entid):
        return (entid in self.followerRanges)



    def _GetFollowTarget(self, entid):
        for (targetEntID, followerList,) in self.followers.iteritems():
            if (entid in followerList):
                return targetEntID




    def _GoToNextNode(self, entity):
        if (entity.entid in self.entitiesPaths):
            oldBlockTrap = stackless.getcurrent().block_trap
            try:
                stackless.getcurrent().block_trap = True
                (instanceID, portalID,) = self.entitiesPaths[entity.entid].pop(0)
                portalTransform = self.portal.GetPortalTransform(instanceID, portalID)
                portalInvTransform = geo2.MatrixInverse(portalTransform)
                entityRadius = entity.movement.GetRadius()
                entityLocalPosition = geo2.Vec3Transform(entity.GetPosition(), portalInvTransform)
                entityMinX = (const.world.PORTAL_BOUNDING_BOX[0][0] + (entityRadius * 2))
                entityMaxX = (const.world.PORTAL_BOUNDING_BOX[1][0] - (entityRadius * 2))
                entityLocalX = min(max(entityMinX, entityLocalPosition[0]), entityMaxX)
                entityLocalPosition = (entityLocalX,
                 0,
                 1)
                nextDestination = geo2.Vec3Transform(entityLocalPosition, portalTransform)
                entity.movement.PushMoveMode(GameWorld.PathToMode(nextDestination[:3], 0.1, False, True))
                self.entitiesWaitingToCrossPortal[entity.entid] = portalID
                if (len(self.entitiesPaths[entity.entid]) == 0):
                    del self.entitiesPaths[entity.entid]

            finally:
                stackless.getcurrent().block_trap = oldBlockTrap

        elif (entity.entid in self.entityDestinations):
            nextDestination = self.entityDestinations[entity.entid]
            entity.movement.PushMoveMode(GameWorld.PathToMode(nextDestination, 0.1, False, True))
            del self.entityDestinations[entity.entid]
        elif (entity.entid in self.followerRanges):
            targetEntID = self._GetFollowTarget(entity.entid)
            targetEnt = self.gameComponentSvc.GetEntityByEntID(targetEntID)
            entity.movement.PushMoveMode(GameWorld.FollowMode(targetEnt.movement, self.followerRanges[entity.entid], False, 0.1))




